/* 
Copyright (c) 2004,2005, 2006, 2007, 2008 University of Illinois 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
AccessibilityExtension.ns(function() { with (AccessibilityExtension.LIB) {
	var AE = AccessibilityExtension.ns;  
	var resetActionAccomplished = false; // global, set to true when at least one keybinding was successfully loaded from the .xul default.
	
	accessextKeybindingManager = function() {
		this.bindings = new Array();
		
		var toAddDot = [
			'accessext.key.standards.w3c.htmlValidator',
			'accessext.key.standards.w3c.cssValidator',
			
			'accessext.key.list.title',
			'accessext.key.list.headers',
			'accessext.key.list.navbars',
			'accessext.key.list.mapElements',
			'accessext.key.list.links',
			'accessext.key.list.forms',
			'accessext.key.list.datatables',
			'accessext.key.list.language',
			'accessext.key.list.frames',
			'accessext.key.list.accesskeys',
			'accessext.key.showDataTableTitles',
			
			'accessext.key.list.images',
			'accessext.key.list.objects',
			'accessext.key.list.abbr',
			'accessext.key.images.replaceWithAlt',
			'accessext.key.expandAbbr',
			'accessext.key.images.hideall',
			'accessext.key.images.hide.backgrounds',
			'accessext.key.objects.hideall',
			'accessext.key.textequivall.hideall',
			
			'accessext.key.styles.author.css.enabled',
			'accessext.key.styles.css.tags.enabled',
			'accessext.key.styles.css.tables.enabled',
			'accessext.key.styles.highcontrast',
			'accessext.key.styles.highcontrast2',
			'accessext.key.styles.userStylesheet.enable',
			
			'accessext.key.keyboardnav.enabled',
			'accessext.key.options.keyboardshortcuts',
			'accessext.key.accesskeys.disable',
			
			'accessext.key.list.events',
			'accessext.key.events.disable.scripting',
			
			'accessext.key.options.MozExtHelp',
			'accessext.key.options.prefPersistence.defaults',
			
			'accessext.key.toggle.typeaheadfind'
		];
	
	
		// Most of the addKeybinding()'s arguments are the same, with "-" replaced with "." from the first to the second
		for(var i=0; i<toAddDot.length; i++) {
			this.addKeybinding(toAddDot[i].replace(/\./g,"-"),toAddDot[i]);
		}
	}
	
	function accessextKeybinding(id,prefString) {
		this.id = id;
		this.prefString = prefString;
	}
	accessextKeybinding.prototype = {
			id : "",
			prefString : "",
			key : false,
			keyName: "",
			modifiers : false,
			modifierSeparatorToken : " ",
			test : "TEST",
			alt : false,
			accel : false,
			shift : false,
			setup : function() {
				return true;
			},
			configureShortcut : function() {
				if (this.key == false) {
					return false;
				}
				var a_str = new Array();
				this.accel = false;
				this.alt = false;
				this.shift = false;
				
				var modifiers = this.modifiers.split(this.modifierSeparatorToken);
			
				for (var i = 0; i < modifiers.length; i++) {
					switch (modifiers[i]) {
						case 'alt': this.alt = true; break;
						case 'accel': this.accel = true; break;
						case 'shift' : this.shift = true; break;
					}
				}
			
				if (this.alt) {
					a_str[a_str.length] = "alt";
				}
				if (this.shift) {
					a_str[a_str.length] = "shift";
				}
				if (this.accel) {
					a_str[a_str.length] = "accel";
				}
			
				var str = a_str.join(this.modifierSeparatorToken);
				if (!(this.accel || this.alt || this.shift)) {
					str="accel" + this.modifierSeparatorToken + "shift";//default modifiers
					this.accel = true;
					this.alt = true;
					
				}
			
				var element = document.getElementById(this.id);
				if (!element) {
					return false;
				}
			
				 //accesext_consoleDump("Setting key " + this.id + "to " + key + " with modifiers " + str);
			
				element.setAttribute("key",this.key);
				element.setAttribute("modifiers",str);
				element.setAttribute("disabled",false);
				return true;
			},
			setKey : function(key) {
				this.key = key;
			},
			setKeyName : function(keyName) {
				this.keyName = keyName;
			},
			getKeyDescription : function() {
				var val = accessext_getBundleSingleString(this.prefString +".description");
				if (val == false) {
					val = "**UNKNOWN**";
				}
				return val;
			},
			setModifiers : function(modifiers) {
				this.alt = this.accel = this.shift = false;
				this.modifiers = modifiers;
				var mods = this.modifiers.split(this.modifierSeparatorToken);
			
				for (var i = 0; i < mods.length; i++) {
					switch (mods[i].toLowerCase()) {
						case 'alt': this.alt = true; break;
						case 'accel': this.accel = true; break;
						case 'shift' : this.shift = true; break;
					}
				}
			},
			getModifierString : function() {
				var a_str = new Array();
				if (this.alt) {
					a_str[a_str.length] = "alt";
				}
				if (this.shift) {
					a_str[a_str.length] = "shift";
				}
				if (this.accel) {
					a_str[a_str.length] = "accel";
				}
			
				var str = a_str.join(this.modifierSeparatorToken);
				return str;
			},
			saveBinding : function() {
				var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
				var k = this.key;
				var mods = this.getModifierString();
				if (this.key == false) {
					k = "";
					mods = "";
				}
				pref.setCharPref(this.prefString,k);
				pref.setCharPref(this.prefString + ".modifiers",mods);
				return true;
			},
			loadBinding : function() {
				// <romanows, 8/3/06> The getElementById code only works when this stuff is called from accessibilityext.js 
				// ... when the extension first loads (because we're out of scope in the keyboardshortcuts dialog.  
				// ... Don't know if this is a bug or a feature.
				
				//Dont do ANY loading until the bindings should exist. navkeys = moz, mainkeyset = FF
				/*if (document.getElementById('navKeys') == null && document.getElementById('mainKeyset') == null) {
					return false;
				}*/
				
				var element = document.getElementById(this.id);
				if (element && element.hasAttribute("key")) {
					this.key = element.getAttribute("key");
					if (element.hasAttribute("modifiers")) {
						this.setModifiers(element.getAttribute("modifiers"));
					}
				}	
		
				// If the reset keybindings preference flag is true, we'll not load from the preferences if 
				// if the keybinding element is in scope.  It is in scope when we first load the extension, so 
				// the reset happens onload.  The reset is completed because onload, configureKeyboardShortcuts() 
				// is called and automatically saves the loaded keybindings.  Thus, we'll automatically unset the 
				// flag after that save.
				var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
				var resetKeybindings = false;
				if( pref.prefHasUserValue("accessext.keybindings.reset.onload") ) {
					resetKeybindings = pref.getBoolPref("accessext.keybindings.reset.onload");
				}
		
				/* Load from preferences, if its there */
				if( !element || !resetKeybindings ) {
					if (pref.prefHasUserValue(this.prefString)) {
						this.key = pref.getCharPref(this.prefString);
						this.setKeyName( pref.getCharPref(this.prefString));
						if (pref.prefHasUserValue(this.prefString + ".modifiers")) {
							this.setModifiers(pref.getCharPref(this.prefString + ".modifiers"))
						} else { 
							try { //SMF 6/3/07
								this.setModifiers('');
							}
							catch (exception) {
								accessext_consoleDump("Key binding " + this.prefString + ".modifiers is problematic.", exception);			
							}
							
						}
						this.configureShortcut();//Make sure value gets committed to the browser
					}
				} else {
					resetActionAccomplished = true;
				}
				return true;
			}
		}
	accessextKeybindingManager.prototype = {
			bindings : Array(),
			configureKeyboardShortcuts : function() {
				for (var i = 0; i < this.bindings.length; i++) {
					var binding = this.bindings[i];
					binding.loadBinding();
					binding.configureShortcut();
					binding.saveBinding();// If it wasn't in preferences, it sure is now.
				}
				if(resetActionAccomplished == true) {
					var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");		
					pref.setBoolPref("accessext.keybindings.reset.onload", false);  // if we were reseting the keybindings, we've done so by now.
				}
			},
			reloadShortcuts : function() {
				for (var i = 0; i < this.bindings.length; i++) {
					var binding = this.bindings[i];
					binding.loadBinding();
					binding.configureShortcut();
				}
			},
			addKeybinding : function(id,pref) {
				this.bindings[this.bindings.length] = new accessextKeybinding(id,pref);
			},
				
		/**
		 * Return the hot key when given a menubar name.
		 * usage:
		 * 		var bindings = new accessextKeybindingManager();
		 * 		bindings.reloadShortcuts(); //otherwise the list seems to be empty of data
		 * 		var hotKey = bindings.getKeybinding('accessext.key.images.hideall');
		 * @return Hot key
		 * @author Sandy Foltz
		*/
			getKeybinding : function(targetPref) {
		 		for (var i = 0; i < this.bindings.length; i++) {
					if (this.bindings[i].prefString == targetPref) {
						var hotKey = this.bindings[i].keyName +  ' ' + this.bindings[i].getModifierString();
		 				return hotKey;
					}
				}
				return '';
			},
			saveToFile : function(filename) {
				var prefs = "";
				for (var i = 0; i < this.bindings.length; i++) {
					var binding = this.bindings[i];
					binding.loadBinding();
					prefs = prefs + binding.prefString + "=" + binding.key + "\n" + binding.prefString + ".modifiers" + "=" + binding.modifiers + "\n";
				}
				writeFile(prefs,"accessextKeyBindings.conf");
			},
			loadFromFile : function(filename) {
				var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); 
				strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
				var bundleBindings = strBundleService.createBundle("file://"+filename);
				for (var i = 0; i < this.bindings.length; i++) {
					if (null != bundleBindings.GetStringFromName(this.bindings[i].prefString)) {
						//accessext_consoleDump("Setting " + this.bindings[i].id + " to " + bundleBindings.GetStringFromName(this.bindings[i].prefString));
						this.bindings[i].setKey(bundleBindings.GetStringFromName(this.bindings[i].prefString));
					}
					if (null != bundleBindings.GetStringFromName(this.bindings[i].prefString + ".modifiers")) {
						this.bindings[i].setModifiers(bundleBindings.GetStringFromName(this.bindings[i].prefString + ".modifiers"));
					}
					this.bindings[i].configureShortcut();
					this.bindings[i].saveBinding();
				}
			}
		}
		
	
AccessibilityExtension.LIB.extend(AccessibilityExtension.LIB,
{
	newAccessextKeybindingManager: function() { 
		var n = new accessextKeybindingManager();
		return n; 
	},
	accessextKeyboardBindingsExport: function() {
		var bindings = new accessextKeybindingManager();
		var filename = "accessextKeyBindings.conf";
		bindings.saveToFile(filename);
		
		var profiledir = AE.DirIO.get('UChrm');
		profiledir.append('accessexttemp');
		profiledir.append(filename);
		accessext_generatePage(AE.DirIO.path(profiledir));
		return true;
	},
	
	// Sets a preferences flag so the keybindings will be reset to their defaults on the next extension load
	// Alerts user to restart Mozilla
	accessextKeyboardBindingsReset: function() {
		var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");		
		pref.setBoolPref("accessext.keybindings.reset.onload", true);  // if we were reseting the keybindings, we've done so by now.
		alert(accessext_getBundleSingleString("accessext.key.onsavebindings"));	
	},
	
	accessextKeyboardBindingsImport: function() {
		var bindings = new accessextKeybindingManager();
		
		var nsIFilePicker = Components.interfaces.nsIFilePicker;
		var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
		fp.init(window, "Select a File", nsIFilePicker.modeOpen);
		fp.appendFilter("Configuration File","*.conf");
		
		var res = fp.show();
		if (res == nsIFilePicker.returnOK){
			var theFile = fp.file;
			bindings.loadFromFile(theFile.path);
			alert(accessext_getBundleSingleString("accessext.key.onsavebindings"));	
		}
		
		return true;
	},
	
	generateSimpleTextElement: function(doc, tagname, text) {
		var element = doc.createElement(tagname);
		element.appendChild(doc.createTextNode(text));
		return element;
	},
	containsModifier: function(node, modifier) {
		if (node.hasAttribute("modifiers")) {
			return (node.getAttribute("modifiers").toUpperCase().indexOf(modifier) >= 0);
		} else {
			return false;
		}
	},
	accessextGetFunctionForKeyNode: function(keyNode) {
		if (keyNode.hasAttribute("command")) {
			return accessext_getBundleString('keybindings.listing.function.command',keyNode.getAttribute("command"));
		} else if (keyNode.hasAttribute("oncommand")) {
			return accessext_getBundleString('keybindings.listing.function.onCommand',keyNode.getAttribute("oncommand"));
		} else if (keyNode.hasAttribute("id")) {
			return accessext_getBundleString('keybindings.listing.function.id',keyNode.getAttribute("id"));
		} else {
			return accessext_getBundleSingleString('keybindings.listing.function.unknown');
		}
	},
	
	accessextGenerateKeyboardBindingListRowForKey: function(doc, keys, table, key, isVk) {
		var alt = null;
		var ctrl= null;
		var altShift = null;
		var altCtrl = null;
		var altCtrlShift = null;
		var ctrlShift = null;
		var shift = null;
		var row = doc.createElement("tr");
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",key));
		
		for (var j = 0; j < keys.length; j ++) {
			var keyFound = false;
			if (isVk) {
				keyFound = keys[j].hasAttribute("keycode") && keys[j].getAttribute("keycode").toUpperCase() == key;
			} else {
				keyFound = keys[j].hasAttribute("key") && keys[j].getAttribute("key").toUpperCase() == key;
			}
			if (keyFound) {
				if (containsModifier(keys[j],"ALT")) {
					if (containsModifier(keys[j],"SHIFT")) {
						if (containsModifier(keys[j],"ACCEL")) {
							altCtrlShift = keys[j];
						} else {
							altShift = keys[j];
						}
					} else if (containsModifier(keys[j],"ACCEL")) {
						altCtrl = keys[j];
					} else { 
						alt = keys[j];
					}
				} else if (containsModifier(keys[j],"ACCEL")) {
					if (containsModifier(keys[j],"SHIFT")) {
						ctrlShift = keys[j];
					} else {
						ctrl = keys[j];
					}
				} else if (containsModifier(keys[j],"SHIFT")) {
					shift = keys[j];
				}
			}
		}
		
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",shift == null ? "" : accessextGetFunctionForKeyNode(shift)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",alt == null ? "" : accessextGetFunctionForKeyNode(alt)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",ctrl == null ? "" : accessextGetFunctionForKeyNode(ctrl)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",altShift == null ? "" : accessextGetFunctionForKeyNode(altShift)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",altCtrl == null ? "" : accessextGetFunctionForKeyNode(altCtrl)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",altCtrlShift == null ? "" : accessextGetFunctionForKeyNode(altCtrlShift)));
		row.appendChild(generateSimpleTextElement(doc.content.document,"td",ctrlShift == null ? "" : accessextGetFunctionForKeyNode(ctrlShift)));
		if (shift != null || alt != null || ctrl != null || altShift != null || altCtrl != null || altCtrlShift != null || ctrlShift != null) {
			return row;
		}
		return null;
	},
	
	accessextGenerateKeyboardBindingList: function() {
		var keys = document.getElementsByTagName("key");
		var doc = accessext_generatePage("");
	
		// <romanows, 8/1/06> changed "doc.content.document." to "doc."
		doc.getElementsByTagName("head")[0].appendChild(generateSimpleTextElement(doc.content.document,"title",accessext_getBundleSingleString('keybindings.listing.title')));
		var style = generateSimpleTextElement(doc.content.document,"style","body {white-space: -moz-pre-wrap !important;}");
		style.setAttribute("type","text/css");
		doc.getElementsByTagName("head")[0].appendChild(style);
	
		var header = doc.createElement("h1");
		header.appendChild(doc.createTextNode(accessext_getBundleSingleString('keybindings.listing.title')));
		doc.body.appendChild(header);
		var summary = doc.createElement("p");
		summary.appendChild(doc.createTextNode(accessext_getBundleString('keybindings.listing.total',keys.length.toString())));
		doc.body.appendChild(summary);
		
		if (keys.length > 0) {
			for (var k = 0; k < keys.length; k++) {
				//doc.body.appendChild(generateSimpleTextElement(doc.content.document,"p","Key: " + keys[k].getAttribute("key") + ", modifiers: " + keys[k].getAttribute("modifiers")));
			}
		
			var table = doc.createElement("table");
			table.setAttribute("border","1");
			var headerRow = doc.createElement("tr");
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.key')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.shift')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.alt')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.accel')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.altShift')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.altAccel')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.altAccelShift')));
			headerRow.appendChild(generateSimpleTextElement(doc.content.document,"th",accessext_getBundleSingleString('keybindings.listing.header.accelShift')));
			
			
			table.appendChild(headerRow);
			var asciiKeys = new Array();
			var i;
			//Load up numbers, capital letters and punctuation
			for (i = 32; i < 97; i++) {
				asciiKeys[asciiKeys.length] = i;
			}
			for (i = 123; i < 127; i++) {
				asciiKeys[asciiKeys.length] = i;
			}
			var vkeys = Array("VK_CANCEL","VK_BACK","VK_TAB","VK_CLEAR","VK_RETURN",
				"VK_ENTER","VK_SHIFT","VK_CONTROL","VK_ALT","VK_PAUSE","VK_CAPS_LOCK",
				"VK_ESCAPE","VK_SPACE","VK_PAGE_UP","VK_PAGE_DOWN","VK_END","VK_HOME","VK_LEFT","VK_UP","VK_RIGHT",
				"VK_DOWN","VK_PRINTSCREEN","VK_INSERT","VK_DELETE","VK_0","VK_1","VK_2","VK_3",
				"VK_4","VK_5","VK_6","VK_7","VK_8","VK_9","VK_SEMICOLON","VK_EQUALS",
				"VK_A","VK_B","VK_C","VK_D","VK_E","VK_F","VK_G","VK_H",
				"VK_I","VK_J","VK_K","VK_L","VK_M","VK_N","VK_O","VK_P",
				"VK_Q","VK_R","VK_S","VK_T","VK_U","VK_V","VK_W","VK_X",
				"VK_Y","VK_Z","VK_NUMPAD0","VK_NUMPAD1","VK_NUMPAD2","VK_NUMPAD3","VK_NUMPAD4","VK_NUMPAD5",
				"VK_NUMPAD6","VK_NUMPAD7","VK_NUMPAD8","VK_NUMPAD9","VK_MULTIPLY","VK_ADD","VK_SEPARATOR","VK_SUBTRACT",
				"VK_DECIMAL","VK_DIVIDE","VK_F1","VK_F2","VK_F3","VK_F4","VK_F5","VK_F6",
				"VK_F7","VK_F8","VK_F9","VK_F10","VK_F11","VK_F12","VK_F13","VK_F14",
				"VK_F15","VK_F16","VK_F17","VK_F18","VK_F19","VK_F20","VK_F21","VK_F22",
				"VK_F23","VK_F24","VK_NUM_LOCK","VK_SCROLL_LOCK","VK_COMMA","VK_PERIOD","VK_SLASH","VK_BACK_QUOTE",
				"VK_OPEN_BRACKET","VK_BACK_SLASH","VK_CLOSE_BRACKET","VK_QUOTE","VK_HELP");
			
			var row ;
			for (i = 0; i < asciiKeys.length; i++) {
				row = accessextGenerateKeyboardBindingListRowForKey(doc,keys,table,String.fromCharCode(asciiKeys[i]),false);
				if (row != null) {
					table.appendChild(row);
				}
			}
			
			for (i = 0; i < vkeys.length; i++) {
				row = accessextGenerateKeyboardBindingListRowForKey(doc,keys,table,vkeys[i],true);
				if (row != null) {
					table.appendChild(row);
				}
			}
			doc.body.appendChild(table);
		}
	}
});
}});	